/*-------------------------------------------------------------------------
 *
 * bufpage.h
 *	  标准 POSTGRES 缓冲区页面定义.
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/storage/bufpage.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef BUFPAGE_H
#define BUFPAGE_H

#include "access/xlogdefs.h"
#include "storage/block.h"
#include "storage/item.h"
#include "storage/off.h"


/*
 * postgres 磁盘页面是建立在 postgres 磁盘块（这只是一个 i/o 单位，见 block.h）之上的一个抽象。
 *
 * 特别地，虽然磁盘块可以是未格式化的，但 postgres 磁盘页面始终是一个槽式页面，形式如下：
 *
 * +----------------+---------------------------------+
 * | PageHeaderData | linp1 linp2 linp3 ...           |
 * +-----------+----+---------------------------------+
 * | ... linpN |									  |
 * +-----------+--------------------------------------+
 * |		   ^ pd_lower							  |
 * |												  |
 * |			 v pd_upper							  |
 * +-------------+------------------------------------+
 * |			 | tupleN ...                         |
 * +-------------+------------------+-----------------+
 * |	   ... tuple3 tuple2 tuple1 | "special space" |
 * +--------------------------------+-----------------+
 *									^ pd_special
 *
 * 当 pd_lower 和 pd_upper 之间无法添加任何内容时，页面是满的。
 *
 * 通过访问方法写出的所有块必须是磁盘页面。
 *
 * 例外：
 *
 * 显然，页面在通过调用 PageInit 初始化之前是未格式化的。
 *
 * 注释：
 *
 * linp1..N 形成一个 ItemId（行指针）数组。 ItemPointers 指向物理块号和该块/页面内的逻辑偏移（行指针号）。 注意，OffsetNumbers
 * 通常从 1 开始，而不是 0。
 *
 * tuple1..N 是“向后”添加到页面上的。 由于使用了 ItemPointer
 * 偏移来访问 ItemId 项而不是实际的字节偏移位置，所以元组可以在页面上物理地重新排列
 * 以应对需要。 这种间接性还使崩溃恢复保持相对简单，因为页面空间
 * 管理的低级细节可以在记录和恢复期间由标准缓冲区页面代码控制。
 *
 * AM 通用的每页面信息存储在 PageHeaderData 中。
 *
 * AM 特定的每页面数据（如果有的话）存储在标记为“特殊空间”的区域；每个 AM 都有一个在某处定义的“透明”结构，作为页面尾部存储。 访问方法应该始终
 * 用 PageInit 初始化其页面，然后设置其自己的透明字段。
 */

typedef Pointer Page;


/*
 * 页面内的位置（字节偏移）。
 *
 * 注意，这实际上限制为 2^15，因为我们将 ItemIdData.lp_off 和 ItemIdData.lp_len 限制为 15 位（见 itemid.h）。
 */
typedef uint16 LocationIndex;


/*
 * 出于历史原因，64 位 LSN 值被存储为两个 32 位
 * 值。
 */
typedef struct
{
	uint32		xlogid;			/* 高位 */
	uint32		xrecoff;		/* low bits */
} PageXLogRecPtr;

#define PageXLogRecPtrGet(val) \
	((uint64) (val).xlogid << 32 | (val).xrecoff)
#define PageXLogRecPtrSet(ptr, lsn) \
	((ptr).xlogid = (uint32) ((lsn) >> 32), (ptr).xrecoff = (uint32) (lsn))

/*
 * 磁盘页面组织
 *
 * 通用的任何页面的空间管理信息
 *
 *		pd_lsn		- 标识此页面最后更改的 xlog 记录。
 *		pd_checksum - 页面校验和，如果已设置。
 *		pd_flags	- 标志位。
 *		pd_lower	- 自由空间起始的偏移量。
 *		pd_upper	- 自由空间结束的偏移量。
 *		pd_special	- 特殊空间起始的偏移量。
 *		pd_pagesize_version - 字节大小和页面布局版本号。
 *		pd_prune_xid - 页面上可能可删除元组中最旧的 XID。
 *
 * LSN 被缓冲区管理器用于强制执行 WAL 的基本规则：
 * “你必须在数据之前写入 xlog”。 脏缓冲区不能被转储到磁盘，直到 xlog 至少已被刷新到页面的 LSN。
 *
 * pd_checksum 存储了页面校验和，如果为此页面设置了校验和；
 * 零是校验和的有效值。如果不使用校验和，则我们将字段保持未设置。 这通常意味着该字段为零
 * ，尽管也可能存在非零值，如果在 9.3 之前的版本中，数据库已
 * 被 pg_upgraded，届时相同的字节偏移用于存储页面最后更新时的当前时间线 ID。
 * 注意，页面上没有指示校验和是否有效的标志，这是一个故意的设计选择，避免依赖于
 * 页面内容来决定是否验证它。因此，没有与校验和相关的标志位。
 *
 * pd_prune_xid 是一个提示字段，有助于确定是否有用的修剪。 目前在索引页面中未使用。
 *
 * 页面版本号和页面大小被打包在一个
 * uint16 字段中。 这是出于历史原因：在 PostgreSQL 7.3 之前，
 * 没有页面版本号的概念，这样做让我们可以假装 7.3 之前的数据库具有版本号零。
 * 我们约束页面大小为 256 的倍数，保留低八位
 * 用于版本号。
 *
 * 最小可能的页面大小可能是 64B，以适应页面头部、透明空间
 * 和一个最小的元组；当然，实际上您希望它大得多，因此
 * 对页面大小模 256 的约束并不是一个重要的限制。
 * 在高端，我们只能支持最大 32KB 的页面，因为 lp_off/lp_len
 * 为 15 位。
 */

typedef struct PageHeaderData
{
	/* XXX LSN 是 *任何* 块的成员，而不仅仅是页面组织的块 */
	PageXLogRecPtr pd_lsn;		
/* LSN: 此页面最后更改的xlog记录后一个字节 */
	uint16		pd_checksum;	/* checksum */
	uint16		pd_flags;		/* 标志位，见下文 */
	LocationIndex pd_lower;		/* 空闲空间开始的偏移量 */
	LocationIndex pd_upper;		/* 空闲空间结束的偏移量 */
	LocationIndex pd_special;	/* 特殊空间开始的偏移量 */
	uint16		pd_pagesize_version;
	TransactionId pd_prune_xid; /* 最旧的可修剪的XID，如果没有则为零 */
	ItemIdData	pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* 行指针数组 */
} PageHeaderData;

typedef PageHeaderData *PageHeader;

/*
 * pd_flags包含以下标志位。未定义的位被初始化为零，并可能在将来使用。
 *
 * 如果pd_lower之前有任何LP_UNUSED行指针，则设置PD_HAS_FREE_LINES。应将其视为提示，而非事实，因为对此的更改未记录在WAL中。
 *
 * 如果UPDATE在页面中没有找到足够的空闲空间来容纳其新元组版本，则设置PD_PAGE_FULL；这表明需要进行修剪。再次强调，这只是一个提示。
 */
#define PD_HAS_FREE_LINES	0x0001	/* 有没有未使用的行指针？ */
#define PD_PAGE_FULL		0x0002	/* 新元组没有足够的空闲空间？ */
#define PD_ALL_VISIBLE		0x0004	/* 页面上的所有元组对每个人都是可见的 */

#define PD_VALID_FLAG_BITS	0x0007	/* 所有有效的 pd_flags 位的或运算 */

/*
 * 页面布局版本号 0 适用于 7.3 之前的 Postgres 版本。
 * 版本 7.3 和 7.4 使用 1，表示新的 HeapTupleHeader 布局。
 * 版本 8.0 使用 2；它再次改变了 HeapTupleHeader 布局。
 * 版本 8.1 使用 3；它重新定义了 HeapTupleHeader 信息掩码位。
 * 版本 8.3 使用 4；它再次改变了 HeapTupleHeader 布局，并
 *		添加了 pd_flags 字段（通过从 pd_tli 中偷出一些位），
 *		以及添加了 pd_prune_xid 字段（这增加了头部的大小）。
 *
 * 从版本 9.3 开始，处理页面时还必须考虑校验和版本。
 */
#define PG_PAGE_LAYOUT_VERSION		4
#define PG_DATA_CHECKSUM_VERSION	1

/* ----------------------------------------------------------------
 *						页面支持宏
 * ----------------------------------------------------------------
 */

/*
 * PageIsValid
 *		如果页面有效则为真。
 */
#define PageIsValid(page) PointerIsValid(page)

/*
 * 行指针不算作头部的一部分
 */
#define SizeOfPageHeaderData (offsetof(PageHeaderData, pd_linp))

/*
 * PageIsEmpty
 *		如果页面上没有分配任何 itemid 则返回真
 */
#define PageIsEmpty(page) \
	(((PageHeader) (page))->pd_lower <= SizeOfPageHeaderData)

/*
 * PageIsNew
 *		如果页面未被初始化（由 PageInit）返回真
 */
#define PageIsNew(page) (((PageHeader) (page))->pd_upper == 0)

/*
 * PageGetItemId
 *		返回页面的项目标识符。
 */
#define PageGetItemId(page, offsetNumber) \
	((ItemId) (&((PageHeader) (page))->pd_linp[(offsetNumber) - 1]))

/*
 * PageGetContents
 *		用于页面不包含行指针的情况。
 *
 * 注意：在 8.3 之前，这不保证生成对齐的结果。
 * 现在是。谨防旧代码可能认为内容的偏移量
 * 只是 SizeOfPageHeaderData 而不是 MAXALIGN(SizeOfPageHeaderData)。
 */
#define PageGetContents(page) \
	((char *) (page) + MAXALIGN(SizeOfPageHeaderData))

/* ----------------
 *		访问页面大小信息的宏
 * ----------------
 */

/*
 * PageSizeIsValid
 *		如果页面大小有效则为真。
 */
#define PageSizeIsValid(pageSize) ((pageSize) == BLCKSZ)

/*
 * PageGetPageSize
 *		返回页面的页面大小。
 *
 * 此宏只能在格式化页面上调用（与 BufferGetPageSize 不同，
 * 可在未格式化页面上调用）。
 * 然而，它可以在未存储在缓存中的页面上调用。
 */
#define PageGetPageSize(page) \
	((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00))

/*
 * PageGetPageLayoutVersion
 *		返回页面的布局版本。
 */
#define PageGetPageLayoutVersion(page) \
	(((PageHeader) (page))->pd_pagesize_version & 0x00FF)

/*
 * PageSetPageSizeAndVersion
 *		设置页面的页面大小和页面布局版本号。
 *
 * 我们可以单独支持设置这两个值，但目前没有
 * 真的必要。
 */
#define PageSetPageSizeAndVersion(page, size, version) \
( \
	AssertMacro(((size) & 0xFF00) == (size)), \
	AssertMacro(((version) & 0x00FF) == (version)), \
	((PageHeader) (page))->pd_pagesize_version = (size) | (version) \
)

/* ----------------
 *		页面特殊数据宏
 * ----------------
 */
/*
 * PageGetSpecialSize
 *		返回页面上特殊空间的大小。
 */
#define PageGetSpecialSize(page) \
	((uint16) (PageGetPageSize(page) - ((PageHeader)(page))->pd_special))

/*
 * 使用断言，验证页面特殊指针是否正常。
 *
 * 此功能旨在捕获在页面初始化之前使用指针的情况。
 * 它作为函数实现，是由于 MSVC 编译器的限制，
 * 它在另一个宏中进行所有这些测试时会出錯。我们
 * 返回真，以便可以使用 AssertMacro()，同时仍然
 * 从该函数中的宏失败中获取具体信息。
 */
static inline bool
PageValidateSpecialPointer(Page page)
{
	Assert(PageIsValid(page));
	Assert(((PageHeader) (page))->pd_special <= BLCKSZ);
	Assert(((PageHeader) (page))->pd_special >= SizeOfPageHeaderData);

	return true;
}

/*
 * PageGetSpecialPointer
 *		返回页面上特殊空间的指针。
 */
#define PageGetSpecialPointer(page) \
( \
	AssertMacro(PageValidateSpecialPointer(page)), \
	(char *) ((char *) (page) + ((PageHeader) (page))->pd_special) \
)

/*
 * PageGetItem
 *		检索给定页面上的项目。
 *
 * 注意：
 *		这并不改变任何传入资源的状态。
 *		语义可能会在未来发生变化。
 */
#define PageGetItem(page, itemId) \
( \
	AssertMacro(PageIsValid(page)), \
	AssertMacro(ItemIdHasStorage(itemId)), \
	(Item)(((char *)(page)) + ItemIdGetOffset(itemId)) \
)

/*
 * PageGetMaxOffsetNumber
 *		返回给定页面使用的最大偏移量。
 *		由于偏移量是基于 1 的，这也代表了页面上的项目数量。
 *
 *		注意：如果页面未被初始化（pd_lower == 0），我们必须
 *		返回零以确保正常行为。接受参数的双重评估
 *		以确保这一点。
 */
#define PageGetMaxOffsetNumber(page) \
	(((PageHeader) (page))->pd_lower <= SizeOfPageHeaderData ? 0 : \
	 ((((PageHeader) (page))->pd_lower - SizeOfPageHeaderData) \
	  / sizeof(ItemIdData)))

/*
 * 额外的宏用于访问页面头。 （注意参数的多重评估！）
 */
#define PageGetLSN(page) \
	PageXLogRecPtrGet(((PageHeader) (page))->pd_lsn)
#define PageSetLSN(page, lsn) \
	PageXLogRecPtrSet(((PageHeader) (page))->pd_lsn, lsn)

#define PageHasFreeLinePointers(page) \
	(((PageHeader) (page))->pd_flags & PD_HAS_FREE_LINES)
#define PageSetHasFreeLinePointers(page) \
	(((PageHeader) (page))->pd_flags |= PD_HAS_FREE_LINES)
#define PageClearHasFreeLinePointers(page) \
	(((PageHeader) (page))->pd_flags &= ~PD_HAS_FREE_LINES)

#define PageIsFull(page) \
	(((PageHeader) (page))->pd_flags & PD_PAGE_FULL)
#define PageSetFull(page) \
	(((PageHeader) (page))->pd_flags |= PD_PAGE_FULL)
#define PageClearFull(page) \
	(((PageHeader) (page))->pd_flags &= ~PD_PAGE_FULL)

#define PageIsAllVisible(page) \
	(((PageHeader) (page))->pd_flags & PD_ALL_VISIBLE)
#define PageSetAllVisible(page) \
	(((PageHeader) (page))->pd_flags |= PD_ALL_VISIBLE)
#define PageClearAllVisible(page) \
	(((PageHeader) (page))->pd_flags &= ~PD_ALL_VISIBLE)

#define PageSetPrunable(page, xid) \
do { \
	Assert(TransactionIdIsNormal(xid)); \
	if (!TransactionIdIsValid(((PageHeader) (page))->pd_prune_xid) || \
		TransactionIdPrecedes(xid, ((PageHeader) (page))->pd_prune_xid)) \
		((PageHeader) (page))->pd_prune_xid = (xid); \
} while (0)
#define PageClearPrunable(page) \
	(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)


/* ----------------------------------------------------------------
 *		extern 声明
 * ----------------------------------------------------------------
 */

/* PageAddItemExtended() 的标志 */
#define PAI_OVERWRITE			(1 << 0)
#define PAI_IS_HEAP				(1 << 1)

/* PageIsVerifiedExtended() 的标志 */
#define PIV_LOG_WARNING			(1 << 0)
#define PIV_REPORT_STAT			(1 << 1)

#define PageAddItem(page, item, size, offsetNumber, overwrite, is_heap) \
	PageAddItemExtended(page, item, size, offsetNumber, \
						((overwrite) ? PAI_OVERWRITE : 0) | \
						((is_heap) ? PAI_IS_HEAP : 0))

#define PageIsVerified(page, blkno) \
	PageIsVerifiedExtended(page, blkno, \
						   PIV_LOG_WARNING | PIV_REPORT_STAT)

/*
 * 检查 BLCKSZ 是否为 sizeof(size_t) 的倍数。
 * 在 PageIsVerifiedExtended() 中，使用本机字大小检查页面是否
 * 满是零要快得多。请注意，此断言被保留在头部中，以确保
 * StaticAssertDecl() 在不同平台和编译器的组合中有效。
 */
StaticAssertDecl(BLCKSZ == ((BLCKSZ / sizeof(size_t)) * sizeof(size_t)),
				 "BLCKSZ has to be a multiple of sizeof(size_t)");

extern void PageInit(Page page, Size pageSize, Size specialSize);
extern bool PageIsVerifiedExtended(Page page, BlockNumber blkno, int flags);
extern OffsetNumber PageAddItemExtended(Page page, Item item, Size size,
										OffsetNumber offsetNumber, int flags);
extern Page PageGetTempPage(Page page);
extern Page PageGetTempPageCopy(Page page);
extern Page PageGetTempPageCopySpecial(Page page);
extern void PageRestoreTempPage(Page tempPage, Page oldPage);
extern void PageRepairFragmentation(Page page);
extern void PageTruncateLinePointerArray(Page page);
extern Size PageGetFreeSpace(Page page);
extern Size PageGetFreeSpaceForMultipleTuples(Page page, int ntups);
extern Size PageGetExactFreeSpace(Page page);
extern Size PageGetHeapFreeSpace(Page page);
extern void PageIndexTupleDelete(Page page, OffsetNumber offset);
extern void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems);
extern void PageIndexTupleDeleteNoCompact(Page page, OffsetNumber offset);
extern bool PageIndexTupleOverwrite(Page page, OffsetNumber offnum,
									Item newtup, Size newsize);
extern char *PageSetChecksumCopy(Page page, BlockNumber blkno);
extern void PageSetChecksumInplace(Page page, BlockNumber blkno);

#endif							/* BUFPAGE_H */
